<?php
/**
 * Sql Parser taken/(quickly|partially] refactored from PHPMyAdmin
 */
class BRail_Application_SqlParser
{
    /**
     * @var string
     */
    protected static $_buffer = '';

    /**
     * @var integer
     */
    private static $_maxLen = 2147483647; // maximum value for intLast Position to tell that the

    /**
     * @var string
     */
    private static $_sqlDelimiter = ';';

    /**
     * Find SQL statements out of a text
     * @param string $sql SQL to be parsed
     * @return array Contains SQL Statements
     */
    public static function getStatements($sql)
    {
        $statements = array();
        /* remove BOM http://en.wikipedia.org/wiki/Byte_order_mark */
        if (substr($sql, 0, 3) == pack("CCC", 0xef, 0xbb, 0xbf)) {
                $sql = substr($sql, 3);
        }

        self::$_buffer = preg_replace('/^[^-\w\/]+/s', '', $sql);

        while ($statement = trim(self::_findNextSqlRequest())) {
            $statements[] = $statement;
        }
        return $statements;
    }

    /**
     * Find next SQL request
     * @return null|string
     */
    protected static function _findNextSqlRequest()
    {
        $sql = '';

        $i = 0; // position in the buffer
        $len = strlen(self::$_buffer);

        while ($i < $len) {
            // Let's find first position of characters of interest
            $firstPosition = self::_findNextC($i);

            $firstSqlDelimiter = strpos(self::$_buffer, self::$_sqlDelimiter, $i);
            if ($firstSqlDelimiter === false) {
                $firstSqlDelimiter = self::$_maxLen;
            } else {
                $foundDelimiter = true;
            }

            // set $i to the position of the first quote, comment.start or delimiter found
            $i = min($firstPosition, $firstSqlDelimiter);

            if ($i == self::$_maxLen) {
                // none of the above was found in the string
                $sql = self::$_buffer;
                self::$_buffer = '';
                return $sql;
            }

            $ch = self::$_buffer[$i];

            // Do we deal with quotes ?
            if (strpos('\'"`', $ch) !== false) {
                $i = self::_findEndingQuote($ch, $i);

                if ($i == self::$_maxLen) {
                    $sql = self::$_buffer;
                    self::$_buffer = '';
                    return $sql;
                }
                continue;
            }

            // Do we deal with comments ?
            // Comments can be #[^\n]*\n or --[^\n]*\n and \/\*(?!\*\/)\*\/
            // @todo find if we do actually need to remove that
            if ($ch == '#'
                    || ($i < ($len - 1) && $ch == '-' && self::$_buffer[$i + 1] == '-'
                            && (($i < ($len - 2) && self::$_buffer[$i + 2] <= ' ')
                                    || ($i == ($len - 1))))
                    || ($i < ($len - 1) && $ch == '/' && self::$_buffer[$i + 1] == '*')
            ) {
                $i = strpos(self::$_buffer, $ch == '/' ? '*/' : "\n", $i);
                // didn't we hit end of string?
                if (false === $i) {
                    $i = $len - 1;
                }
                // Skip * out of */
                if ($ch == '/') {
                    $i++;
                }

                // Skip last char
                $i++;

                // Aren't we at the end?
                if ($i <= $len) {
                    $i--;
                }
                continue;
            }

            // We should be and the end of SQL
            $sql = substr(self::$_buffer, 0, $i);
            self::$_buffer = substr(self::$_buffer, $i + 1);
            return $sql;
        }
        return null;
    }

    /**
     * Find the ending quote from buffer
     * @param string $quote Starting quote
     * @param integer $i position to start in the buffer
     * @return <type>
     */
    protected static function _findEndingQuote($quote, $i)
    {
        $endq = false;
        while (!$endq) {
            // Find next quote
            $pos = strpos(self::$_buffer, $quote, $i + 1);
            // No quote? Too short string

            if ($pos === false) {
                return self::$_maxLen;
            }
            // Was not the quote escaped?
            $j = $pos - 1;
            while (self::$_buffer[$j] == '\\') $j--;
            // Even count means it was not escaped
            $endq = (((($pos - 1) - $j) % 2) == 0);

            // Skip the string
            $i = $pos + 1;
        }
        return $i;
    }

    /**
     * Find the position of the next character of interest
     * @param integer $i start position
     * @return integer position 
     */
    private static function _findNextC($i)
    {
        if (preg_match('/(\'|"|#|-- |\/\*|`|(?i)DELIMITER)/', self::$_buffer, $matches, PREG_OFFSET_CAPTURE, $i)) {
            return $matches[1][1];
        }
        return self::$_maxLen;
    }
}